package nbcp.base.model

import com.jcraft.jsch.ChannelExec
import com.jcraft.jsch.ChannelSftp
import com.jcraft.jsch.JSch
import com.jcraft.jsch.Session
import nbcp.base.comm.ApiResult
import nbcp.base.extend.*
import nbcp.base.utils.FileUtil
import org.slf4j.LoggerFactory
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit


class SshClient {
    companion object {
        private val logger = LoggerFactory.getLogger(this::class.java.declaringClass)
    }

    var host: String = ""
    var userName: String = "root"
    var password: String = ""
    var port: Int = 22

    val uploadExecutor = Executors.newFixedThreadPool(1);
    private lateinit var jschSession: Session

    fun isConnected(): Boolean {
        if (!this::jschSession.isInitialized) {
            return false;
        }

        return jschSession.isConnected
    }

    fun disconnect() {
        if (isConnected()) {
            jschSession.disconnect()
        }
    }

    fun sshConnect(): Session {
        val jSch = JSch()
        if (isConnected()) {
            return jschSession
        }

        jschSession = jSch.getSession(userName, host, port)
        jschSession.setPassword(password)

        val config = Properties()
        config["StrictHostKeyChecking"] = "no"
        jschSession.setConfig(config)
        jschSession.timeout = 3000

        jschSession.connect()
        return jschSession
    }


    fun exec(command: String): ApiResult<String> {
        var session = sshConnect();
        var result = "";
        var exitStatus = 0

        logger.Important("执行远程命令(${host}): ${command}")

        try {
            val jschChannel = session.openChannel("exec")
            (jschChannel as ChannelExec).setCommand(command)

            jschChannel.setInputStream(null)
            // 错误信息输出流，用于输出错误的信息，当exitstatus<0的时候

            var byteMem = ByteArrayOutputStream()
            jschChannel.setErrStream(byteMem)

            // 执行命令，等待执行结果
            jschChannel.connect()

            // 获取命令执行结果
            result = jschChannel.inputStream.readContentString()
            exitStatus = jschChannel.getExitStatus();

            if (exitStatus != 0) {
                result = byteMem.toByteArray().toUtf8String()
            }

        } catch (e: Exception) {
            throw RuntimeException(e)
        }

        if (exitStatus == 0) {
            return ApiResult.of(result)
        }
        return ApiResult.error(result, exitStatus);
    }

    fun scp(
        sourceFile: File,
        targetPath: String,
        timeoutSeconds: Int = 30,
        progress: ((Int) -> Unit)? = null
    ): ApiResult<String> {
        var result = ""
        var session = sshConnect();

        logger.Important("上传文件: ${sourceFile.absolutePath} --(${host})--> ${targetPath}")
        try {
            val chSftp = session.openChannel("sftp") as ChannelSftp
            chSftp.connect()
            chSftp.setFilenameEncoding("UTF-8")

            if (sourceFile.exists() == false) {
                throw RuntimeException("${sourceFile.path} 文件不存在");
            }
            if (sourceFile.isFile) {
                result = doScpFile(chSftp, sourceFile, targetPath, timeoutSeconds, progress)
            } else {
                //递归处理
                sourceFile.walkBottomUp().onEnter { path ->
                    var targetRealPath = getRealPath(sourceFile, targetPath, path);
                    mkdirs(chSftp, targetRealPath);

                    path.listFiles().forEach { file ->
                        if (file.isFile) {
                            var targetRealPath = getRealPath(sourceFile, targetPath, file);
                            result = doScpFile(chSftp, sourceFile, targetRealPath, timeoutSeconds, progress)

                            if (result.HasValue) {
                                return@onEnter false
                            }
                        }
                    }

                    return@onEnter true
                }
            }
        } catch (e: Exception) {
            throw RuntimeException(e)
        }

        return ApiResult.error(result);
    }


    private fun doScpFile(
        chSftp: ChannelSftp,
        sourceFile: File,
        targetPath: String,
        timeoutSeconds: Int,
        progress: ((Int) -> Unit)?
    ): String {

        mkdirs(chSftp, FileUtil.resolvePath(targetPath, "../"));

        var targetFile = targetPath;
        if (chSftp.stat(targetFile).isDir) {
            targetFile += "/" + sourceFile.name
        }

        var progressMonitor = DefaultSftpProgressMonitor(progress);

        uploadExecutor.execute {
            chSftp.put(sourceFile.path, targetFile, progressMonitor, ChannelSftp.OVERWRITE)
        }

        uploadExecutor.shutdown();
        try {
            if (!uploadExecutor.awaitTermination(timeoutSeconds.toLong(), TimeUnit.SECONDS)) {
                uploadExecutor.shutdownNow();
            }
        } catch (e: InterruptedException) {
            uploadExecutor.shutdownNow();
        }

        chSftp.disconnect()
        if (progressMonitor.progress == 100) {
            return "";
        }
        return "上传进度: ${progressMonitor.progress}%, 超时未完成!";
    }

    /**
     * 获取相对位置
     */
    private fun getRealPath(rootPath: File, targetPath: String, file: File): String {
        if (!file.path.startsWith(rootPath.path)) {
            throw RuntimeException("${file.path} 必须以 ${rootPath.path} 开头")
        }

        return FileUtil.resolvePath(targetPath, file.path.Slice(rootPath.path.length));
    }

    private fun mkdirs(chSftp: ChannelSftp, targetPath: String) {
        try {
            if (chSftp.stat(targetPath).isDir == false) {
                chSftp.mkdir(targetPath)
            }
        } catch (e: Exception) {
            chSftp.mkdir(targetPath)
        }
    }
}
